package com.wangp.demo.anno.processor;

import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.comp.Enter;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.comp.MemberEnter;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;
import com.wangp.demo.anno.IValueChangeTrace;
import com.wangp.demo.anno.ValueChangeTrace;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;

/**
 * @author <a href="mailto:wangpeng@i-baby.com" rel="nofollow">wp☠😘✨</a>
 * @version 0.0.1
 * @since 2025/8/14 22:31
 */
public class ValueChangeTraceProcessor extends AbstractProcessor {

    private static final String GEN_LAZY_CHECK_FUNC_PREFIX = "$$_set_value_change_check_";

    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private Names names;
    private Symtab symtab;
    private MemberEnter memberEnter;
    private Enter enter;

    /**
     * 开启详细输出
     */
    private boolean verbose;

    /**
     * 忽略的字段
     */
    private Set<String> ignoreField;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        ProcessingEnvironment actualProcessingEnv = ProcessEnvUtil.jbUnwrap(ProcessingEnvironment.class, processingEnv);
        ProcessEnvUtil.prepareEvn();
        this.javacTrees = JavacTrees.instance(actualProcessingEnv);
        Context context = ((JavacProcessingEnvironment) actualProcessingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
        this.symtab = Symtab.instance(context);
        this.memberEnter = MemberEnter.instance(context);
        this.enter = Enter.instance(context);
        parseOptions(processingEnv.getOptions());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (annotations == null || annotations.isEmpty() || roundEnv.processingOver()) {
            return false;
        }
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ValueChangeTrace.class);
        try {
            for (Element element : elements) {
                switch (element.getKind()) {
                    case CLASS -> doProcessForClass(element);
                    case FIELD -> {
                        Set<? extends Element> rootElements = roundEnv.getRootElements();
                        Map<Name, Symbol.ClassSymbol> classeMap = new HashMap<>();
                        for (Element e : rootElements) {
                            if (e.getKind() == ElementKind.CLASS) {
                                Symbol.ClassSymbol symbol = (Symbol.ClassSymbol) e;
                                classeMap.put(symbol.getQualifiedName(), symbol);
                            }
                        }
                        doProcessForField(element, classeMap);
                    }
                    default -> {}
                }
            }
        } catch (Throwable t) {
            t.printStackTrace(System.out);
            throw new RuntimeException(t);
        }
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of(ValueChangeTrace.class.getName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_17;
    }

    private void parseOptions(Map<String, String> options) {
        verbose = Boolean.parseBoolean(Objects.toString(options.get("workOrder.annotationProcessor.verbose")));
        var ignoreFieldStr = options.get("workOrder.annotationProcessor.ignoreField");
        if (ignoreFieldStr != null && !ignoreFieldStr.isBlank()) {
            ignoreField = new HashSet<>(Arrays.asList(ignoreFieldStr.split(",")));
        } else {
            ignoreField = Collections.emptySet();
        }
    }

    private void doProcessForClass(Element element) {
        JCTree.JCClassDecl jcClassDecl = ((JCTree.JCClassDecl) javacTrees.getTree(element));
        resetPos(jcClassDecl);
        Symbol.ClassSymbol classSymbol = (Symbol.ClassSymbol) element;
        ValueChangeTrace valueChangeTrace = classSymbol.getAnnotation(ValueChangeTrace.class);
        if (verbose) {
            System.out.printf("开始处理：%s\n", classSymbol.getQualifiedName());
        }
        List<JCTree.JCMethodDecl> defineMethods = List.nil();
        Set<String> fieldNames = new HashSet<>();
        var annoIgnoreFields = Arrays.asList(valueChangeTrace.ignoreField());
        for (Symbol it : classSymbol.members().getSymbols()) {
            if (it.getKind() == ElementKind.FIELD) {
                String fn = it.getSimpleName().toString();
                if (this.ignoreField.contains(fn) || annoIgnoreFields.contains(fn)) {
                    continue;
                }
                if ((it.flags() & Flags.TRANSIENT) != 0) {
                    continue;
                }
                if ((it.flags() & Flags.FINAL) != 0) {
                    continue;
                }
                if ((it.flags() & Flags.STATIC) != 0) {
                    continue;
                }
                fieldNames.add(it.getSimpleName().toString());
            }
        }
        for (Symbol symbol : classSymbol.members().getSymbols()) {
            switch (symbol.getKind()) {
                case FIELD -> {
                    if (fieldNames.contains(symbol.getSimpleName().toString())) {
                        defineMethods = defineMethods.append(defineLazyCheckSetFunc(((Symbol.VarSymbol) symbol)));
                    }
                }
                case METHOD -> {
                    Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) symbol;
                    for (JCTree t : jcClassDecl.defs) {
                        if (t.getKind() == Tree.Kind.METHOD) {
                            JCTree.JCMethodDecl methodDecl = (JCTree.JCMethodDecl) t;
                            if (methodDecl.getName().equals(methodSymbol.getSimpleName())) {
                                updateOriginFunc(methodDecl, fieldNames);
                                break;
                            }
                        }
                    }
                }
                default -> {}
            }
        }
        implementLazyCheck(classSymbol, jcClassDecl);
        for (JCTree.JCMethodDecl defineMethod : defineMethods) {
            jcClassDecl.defs = jcClassDecl.defs.append(defineMethod);
            doMemberEnter(classSymbol, defineMethod);
        }
        if (verbose) {
            System.out.printf("处理后的类：%s\n", jcClassDecl);
        }
    }

    private void doProcessForField(Element element, Map<Name, Symbol.ClassSymbol> classeMap) {
        var varSymbol = (Symbol.VarSymbol) element;
        var classSymbol = classeMap.get(varSymbol.owner.getQualifiedName());
        var jcClassDecl = javacTrees.getTree(classSymbol);
        resetPos(jcClassDecl);
        for (Symbol symbol : classSymbol.members().getSymbols(it -> it.getKind() == ElementKind.METHOD)) {
            Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) symbol;
            for (JCTree t : jcClassDecl.defs) {
                if (t.getKind() == Tree.Kind.METHOD) {
                    JCTree.JCMethodDecl methodDecl = (JCTree.JCMethodDecl) t;
                    if (methodDecl.getName().equals(methodSymbol.getSimpleName())) {
                        updateOriginFunc(methodDecl, Collections.singletonList(varSymbol.getSimpleName().toString()));
                        break;
                    }
                }
            }
        }
        implementLazyCheck(classSymbol, jcClassDecl);
        JCTree.JCMethodDecl methodDecl = defineLazyCheckSetFunc(varSymbol);
        jcClassDecl.defs = jcClassDecl.defs.append(methodDecl);
        doMemberEnter(classSymbol, methodDecl);
        if (verbose) {
            System.out.printf("处理后的类：%s\n", jcClassDecl);
        }
    }

    private void implementLazyCheck(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl) {
        boolean hasInterface = false;
        for (Type i : classSymbol.getInterfaces()) {
            if (i.asElement().getQualifiedName().toString().equals(IValueChangeTrace.class.getName())) {
                hasInterface = true;
            }
        }
        if (hasInterface) {
            return;
        }
        var forSuper = classSymbol.getSuperclass().asElement().getAnnotation(ValueChangeTrace.class) != null;
        valueChangeField(classSymbol, jcClassDecl);
        loadField(classSymbol, jcClassDecl);
        restChangeValueFunc(classSymbol, jcClassDecl, forSuper);
        isChangeFunc(classSymbol, jcClassDecl, forSuper);
        isLoadFunc(classSymbol, jcClassDecl, forSuper);
        loadFunc(classSymbol, jcClassDecl, forSuper);
        valueChangeCallbackFunc(classSymbol, jcClassDecl);
        jcClassDecl.implementing = jcClassDecl.implementing.append(createTypeExpression(IValueChangeTrace.class.getName()));
    }

    private void valueChangeField(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl) {
        JCTree.JCVariableDecl varDef = treeMaker.VarDef(
            treeMaker.Modifiers(Flags.PRIVATE | Flags.TRANSIENT),
            names.fromString(IValueChangeTrace.VALUE_CHANGE_FIELD_NAME),
            treeMaker.Type(symtab.booleanType),
            null
        );
        jcClassDecl.defs = jcClassDecl.defs.append(varDef);
        doMemberEnter(classSymbol, varDef);
    }

    private void loadField(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl) {
        JCTree.JCVariableDecl varDef = treeMaker.VarDef(
            treeMaker.Modifiers(Flags.PRIVATE | Flags.TRANSIENT),
            names.fromString(IValueChangeTrace.LOAD_FIELD_NAME),
            treeMaker.Type(symtab.booleanType),
            null
        );
        jcClassDecl.defs = jcClassDecl.defs.append(varDef);
        doMemberEnter(classSymbol, varDef);
    }

    private void restChangeValueFunc(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl, boolean forSuper) {
        // this.$$_value_change_check_value_change = false
        var fieldName = names.fromString(IValueChangeTrace.VALUE_CHANGE_FIELD_NAME);
        var funName = names.fromString(IValueChangeTrace.RESET_CHANGE_VALUE_FUNC_NAME);
        forVoidFun(classSymbol, jcClassDecl, forSuper, fieldName, funName, false);
    }

    private void isChangeFunc(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl, boolean forSuper) {
        var fieldName = names.fromString(IValueChangeTrace.VALUE_CHANGE_FIELD_NAME);
        var funName = names.fromString(IValueChangeTrace.IS_CHANGE_FUNC_NAME);
        JCTree.JCExpression valueExpr;
        if (forSuper) {
            valueExpr = treeMaker.Binary(
                JCTree.Tag.OR,
                treeMaker.Apply(List.nil(), treeMaker.Select(superExpr(), funName), List.nil()),
                treeMaker.Select(thisExpr(), fieldName)
            );
        } else {
            valueExpr = treeMaker.Select(thisExpr(), fieldName);
        }
        forBoolFun(classSymbol, jcClassDecl, funName, valueExpr);
    }

    private void forBoolFun(
        Symbol.ClassSymbol classSymbol,
        JCTree.JCClassDecl jcClassDecl,
        com.sun.tools.javac.util.Name funName,
        JCTree.JCExpression valueExpr
    ) {
        var methodBody = treeMaker.Block(0, List.of(treeMaker.Return(valueExpr)));
        JCTree.JCMethodDecl methodDecl = treeMaker.MethodDef(
            treeMaker.Modifiers(Flags.PUBLIC),
            funName,
            treeMaker.Type(symtab.booleanType),
            List.nil(),
            List.nil(),
            List.nil(),
            methodBody,
            null
        );
        jcClassDecl.defs = jcClassDecl.defs.append(methodDecl);
        doMemberEnter(classSymbol, methodDecl);
    }

    private void valueChangeCallbackFunc(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl) {
        var funcName = names.fromString(IValueChangeTrace.VALUE_CHANGE_CALLBACK_FUNC_NAME);
        Predicate<Symbol> filterFunc = symbol -> {
            if (symbol.getKind() == ElementKind.METHOD && symbol.getSimpleName().equals(funcName)) {
                Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) symbol;
                List<Symbol.VarSymbol> parameters = methodSymbol.getParameters();
                if (parameters.size() != 4) {
                    return false;
                }
                return (
                    parameters.get(0).type.tsym.getQualifiedName().equals(symtab.stringType.tsym.getQualifiedName()) &&
                    parameters.get(1).type.tsym.getQualifiedName().equals(symtab.stringType.tsym.getQualifiedName()) &&
                    parameters.get(2).type.tsym.getQualifiedName().equals(symtab.objectType.tsym.getQualifiedName()) &&
                    parameters.get(3).type.tsym.getQualifiedName().equals(symtab.objectType.tsym.getQualifiedName())
                );
            }
            return false;
        };
        // 判断本类是否重写了这个方法
        if (classSymbol.members().getSymbols(filterFunc).iterator().hasNext()) {
            return;
        }
        boolean callSuper = false;
        // 判断父类是否实现了这个方法
        Type superclassType = classSymbol.getSuperclass();
        if (
            !superclassType.equals(Type.noType) &&
            !superclassType.isInterface() &&
            superclassType.asElement() instanceof Symbol.ClassSymbol superClassSymbol &&
            !superClassSymbol.getQualifiedName().toString().equals("java.lang.Object")
        ) {
            callSuper = superClassSymbol.members().getSymbols(filterFunc).iterator().hasNext();
        }
        var methodBody = treeMaker.Block(
            0,
            callSuper
                ? List.of(
                    treeMaker.Exec(
                        treeMaker.Apply(
                            List.nil(),
                            treeMaker.Select(superExpr(), funcName),
                            List.of(
                                treeMaker.Ident(names.fromString("type")),
                                treeMaker.Ident(names.fromString("field")),
                                treeMaker.Ident(names.fromString("oldValue")),
                                treeMaker.Ident(names.fromString("newValue"))
                            )
                        )
                    )
                )
                : List.nil()
        );
        JCTree.JCMethodDecl methodDecl = treeMaker.MethodDef(
            treeMaker.Modifiers(Flags.PUBLIC),
            names.fromString(IValueChangeTrace.VALUE_CHANGE_CALLBACK_FUNC_NAME),
            treeMaker.Type(symtab.voidType),
            List.nil(),
            List.of(
                treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("type"), treeMaker.Type(symtab.stringType), null),
                treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("field"), treeMaker.Type(symtab.stringType), null),
                treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.PARAMETER),
                    names.fromString("oldValue"),
                    treeMaker.Type(symtab.objectType),
                    null
                ),
                treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.PARAMETER),
                    names.fromString("newValue"),
                    treeMaker.Type(symtab.objectType),
                    null
                )
            ),
            List.nil(),
            methodBody,
            null
        );
        jcClassDecl.defs = jcClassDecl.defs.append(methodDecl);
        doMemberEnter(classSymbol, methodDecl);
    }

    private void isLoadFunc(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl, boolean forSuper) {
        var fieldName = names.fromString(IValueChangeTrace.LOAD_FIELD_NAME);
        var funName = names.fromString(IValueChangeTrace.IS_LOAD_FUNC_NAME);
        JCTree.JCExpression valueExpr;
        if (forSuper) {
            valueExpr = treeMaker.Binary(
                JCTree.Tag.AND,
                treeMaker.Apply(List.nil(), treeMaker.Select(superExpr(), funName), List.nil()),
                treeMaker.Select(thisExpr(), fieldName)
            );
        } else {
            valueExpr = treeMaker.Select(thisExpr(), fieldName);
        }
        forBoolFun(classSymbol, jcClassDecl, funName, valueExpr);
    }

    private void loadFunc(Symbol.ClassSymbol classSymbol, JCTree.JCClassDecl jcClassDecl, boolean forSuper) {
        // this.$$_value_change_check_load = false
        var fieldName = names.fromString(IValueChangeTrace.LOAD_FIELD_NAME);
        var funName = names.fromString(IValueChangeTrace.LOAD_FUNC_NAME);
        forVoidFun(classSymbol, jcClassDecl, forSuper, fieldName, funName, true);
    }

    private void forVoidFun(
        Symbol.ClassSymbol classSymbol,
        JCTree.JCClassDecl jcClassDecl,
        boolean forSuper,
        com.sun.tools.javac.util.Name fieldName,
        com.sun.tools.javac.util.Name funName,
        boolean value
    ) {
        List<JCTree.JCStatement> execs = List.of(
            treeMaker.Exec(treeMaker.Assign(treeMaker.Select(thisExpr(), fieldName), treeMaker.Literal(value)))
        );
        if (forSuper) {
            execs = execs.prepend(treeMaker.Exec(treeMaker.Apply(List.nil(), treeMaker.Select(superExpr(), funName), List.nil())));
        }
        var methodBody = treeMaker.Block(0, execs);
        JCTree.JCMethodDecl methodDecl = treeMaker.MethodDef(
            treeMaker.Modifiers(Flags.PUBLIC),
            funName,
            treeMaker.Type(symtab.voidType),
            List.nil(),
            List.nil(),
            List.nil(),
            methodBody,
            null
        );
        jcClassDecl.defs = jcClassDecl.defs.append(methodDecl);
        doMemberEnter(classSymbol, methodDecl);
    }

    private JCTree.JCExpression thisExpr() {
        return treeMaker.Ident(names.fromString("this"));
    }

    private JCTree.JCExpression superExpr() {
        return treeMaker.Ident(names.fromString("super"));
    }

    private JCTree.JCExpression createTypeExpression(String typeName) {
        if (typeName.contains(".")) {
            String[] parts = typeName.split("\\.");
            JCTree.JCExpression expr = treeMaker.Ident(names.fromString(parts[0]));

            for (int i = 1; i < parts.length; i++) {
                expr = treeMaker.Select(expr, names.fromString(parts[i]));
            }

            return expr;
        } else {
            return treeMaker.Ident(names.fromString(typeName));
        }
    }

    private JCTree.JCMethodDecl defineLazyCheckSetFunc(Symbol.VarSymbol varSymbol) {
        JCTree.JCAssign setFieldValue = treeMaker.Assign(
            treeMaker.Select(thisExpr(), varSymbol.getSimpleName()),
            treeMaker.Ident(varSymbol.getSimpleName())
        );
        com.sun.tools.javac.util.Name tempName = names.fromString("temp");
        JCTree.JCBlock methodBody = treeMaker.Block(
            0,
            List.of(
                treeMaker.If(
                    treeMaker.Select(thisExpr(), names.fromString(IValueChangeTrace.LOAD_FIELD_NAME)),
                    treeMaker.Block(
                        0,
                        List.of(
                            // temp = this.field
                            treeMaker.VarDef(
                                treeMaker.Modifiers(0),
                                tempName,
                                treeMaker.Type(varSymbol.asType()),
                                treeMaker.Select(thisExpr(), varSymbol.getSimpleName())
                            ),
                            // this.field = value || setSuperField(value)
                            treeMaker.Exec(setFieldValue),
                            // if ()
                            treeMaker.If(
                                // !Objects.equals(temp,this.field) ||
                                // !Objects.equals(temp,super.getFunc())
                                treeMaker.Unary(
                                    JCTree.Tag.NOT,
                                    treeMaker.Apply(
                                        List.nil(),
                                        treeMaker.Select(
                                            treeMaker.Select(
                                                treeMaker.Select(treeMaker.Ident(names.fromString("java")), names.fromString("util")),
                                                names.fromString("Objects")
                                            ),
                                            names.fromString("equals")
                                        ),
                                        List.of(treeMaker.Ident(tempName), treeMaker.Select(thisExpr(), varSymbol.getSimpleName()))
                                    )
                                ),
                                treeMaker.Block(
                                    0,
                                    List.of(
                                        // this.VALUE_CHANGE_FIELD = true
                                        treeMaker.Exec(
                                            treeMaker.Assign(
                                                treeMaker.Select(thisExpr(), names.fromString(IValueChangeTrace.VALUE_CHANGE_FIELD_NAME)),
                                                treeMaker.Literal(true)
                                            )
                                        ),
                                        // valueChangeCallback(field,temp,this.field)
                                        treeMaker.Exec(
                                            treeMaker.Apply(
                                                List.nil(),
                                                treeMaker.Select(
                                                    thisExpr(),
                                                    names.fromString(IValueChangeTrace.VALUE_CHANGE_CALLBACK_FUNC_NAME)
                                                ),
                                                List.of(
                                                    treeMaker.Literal(varSymbol.owner.getQualifiedName().toString()),
                                                    treeMaker.Literal(varSymbol.getSimpleName().toString()),
                                                    treeMaker.Ident(tempName),
                                                    treeMaker.Select(thisExpr(), varSymbol.getSimpleName())
                                                )
                                            )
                                        )
                                    )
                                ),
                                null
                            )
                        )
                    ),
                    treeMaker.Block(0, List.of(treeMaker.Exec(setFieldValue)))
                )
            )
        );
        var methodName = names.fromString(GEN_LAZY_CHECK_FUNC_PREFIX + varSymbol.getSimpleName());
        return treeMaker.MethodDef(
            treeMaker.Modifiers(Flags.PUBLIC),
            methodName,
            treeMaker.Type(symtab.voidType),
            List.nil(),
            List.of(
                treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.FINAL | Flags.PARAMETER),
                    varSymbol.getSimpleName(),
                    treeMaker.Type(varSymbol.asType()),
                    null
                )
            ),
            List.nil(),
            methodBody,
            null
        );
    }

    private void updateOriginFunc(JCTree.JCMethodDecl funcDecl, Collection<String> selfFields) {
        var originStatements = funcDecl.getBody().stats;
        var newStatements = List.<JCTree.JCStatement>nil();
        for (JCTree.JCStatement os : originStatements) {
            boolean isReplace = false;
            if (os.getKind() == Tree.Kind.EXPRESSION_STATEMENT) {
                JCTree.JCExpressionStatement statement = (JCTree.JCExpressionStatement) os;
                // this.value =
                if (statement.expr instanceof JCTree.JCAssign jcAssign) {
                    if (jcAssign.lhs instanceof JCTree.JCFieldAccess jcFieldAccess) {
                        if (selfFields.contains(jcFieldAccess.name.toString())) {
                            newStatements = newStatements.append(
                                treeMaker.Exec(
                                    treeMaker.Apply(
                                        List.nil(),
                                        treeMaker.Select(thisExpr(), names.fromString(GEN_LAZY_CHECK_FUNC_PREFIX + jcFieldAccess.name)),
                                        List.of(jcAssign.rhs)
                                    )
                                )
                            );
                            isReplace = true;
                        }
                    }
                }
            }
            if (!isReplace) {
                newStatements = newStatements.append(os);
            }
        }
        funcDecl.getBody().stats = newStatements;
    }

    private void doMemberEnter(Symbol.ClassSymbol classSymbol, JCTree jcTree) {
        Method func = Permit.permissiveGetMethod(MemberEnter.class, "memberEnter", JCTree.class, Env.class);
        Permit.invokeSneaky(func, memberEnter, jcTree, enter.getEnv(classSymbol));
    }

    private void resetPos(JCTree.JCClassDecl jcClassDecl) {
        int pos = treeMaker.pos;
        for (JCTree member : jcClassDecl.getMembers()) {
            pos = Math.max(pos, member.pos);
        }
        treeMaker.pos = pos;
    }
}
